package ControlComponents;

import java.util.Scanner;

import DataFormatConversionComponents.NumberConverter;
import Peripherals.IODeviceController;
import StorageComponents.Accumulator;
import StorageComponents.Memory;
import StorageComponents.Registers;

/**
 * This class simulates the control unit component of the AR2RISC. It has access to the main memory,
 * the instruction register, the status register, program counter, general purpose registers, accumulator,
 * io devices, ect...
 *
 *
 */

public class ControlUnit {

	private Memory mem;
	private InstructionRegister ir;
	private StatusRegister sr;
	private ProgramCounter pc;
	private Registers regs;
	private Accumulator acc;
	private ALU alu;
	private IODeviceController io;
	private boolean stopFlag;
	private int mode; //mode = 0 for debug mode && mode  = 1 for complete execution of input file 
	private int instructionCount;
	
	/**
	 * 
	 * @param mem
	 * @param ir
	 * @param sr
	 * @param pc
	 * @param regs
	 * @param acc
	 * @param alu
	 * @param io
	 * @param mode
	 */
	public ControlUnit( Memory mem, InstructionRegister ir, StatusRegister sr, ProgramCounter pc, Registers regs, 
			Accumulator acc, ALU alu, IODeviceController io, int mode){
		this.mem = mem;
		this.ir = ir;
		this.sr = sr;
		this.pc = pc;
		this.regs = regs;
		this.acc = acc;	
		this.alu = alu;
		this.io = io;
		this.mode = mode;
		stopFlag = false;
	}

	/**
	 * Perform the fetch cycle
	 */
	public void fetch(){

		instructionCount--;	

		//fetch the instruction
		int[] instruction = mem.readInstruction( pc.getIntValue() );
		ir.set(instruction);


		//incrementing the PC by two
		//making a copy of the status register
		int temp_acc[] = new int[8];
		for(int k = 0; k<8 ; k++){
			temp_acc[k]= acc.get()[k];
		}
		
		//making a copy of the status register
		int temp_streg[]= new int[4];

		temp_streg[0] = sr.getZero();
		temp_streg[1] = sr.getCarry();
		temp_streg[2] = sr.getNegative();
		temp_streg[3] = sr.getOverflow();
			        
		int temp_pc[] = new int[8];
		for( int pos = 0; pos < 8; pos++ )
			temp_pc[pos] = pc.get()[pos];
		
		acc.set(temp_pc);
		
		int temp_carry = sr.getCarry();	
		sr.clearCarry();

		/**
		 * no se debe necesitar
		 */
		//alu.setAccumulator(acc.get());
		int temp_two[] = { 0, 0, 0, 0, 0, 0, 1, 0 };
		alu.addc( temp_two );

		int temp_acc2[] = new int[8];
		for( int pos = 0; pos < 8; pos++ )
			temp_acc2[pos] = acc.get()[pos];
		
		pc.set(temp_acc2);
		
		//restoring previous state
		acc.set(temp_acc);
		sr.set(temp_streg);

		if( temp_carry == 1 )
			sr.setCarry();
		
		//execute instruction

		execute();				
	}

	/**
	 * Peform the execute cycle
	 */
	public void execute(){
		//integers for: a register's index within the Registers class array structure, its content's decimal value,
		//and the address used in the direct addressing mode (bits 0-7 from the instruction register)
		int regIndex, regContent, directAddress;
		//integer arrays for the 8 bit register being used in instructions, the byte to be read from memory, and the address used
		//in the direct addressing mode (bits 0-7 from the instruction register)
		int[] register, readResult,  immediateContent;
		NumberConverter converter = new NumberConverter();
		ControlInstructions branchType = new ControlInstructions(this.pc, this.sr);
		int opcode = ir.getOpcode();
		
		switch( opcode ){

		
		//hay 20 casos; un caso para cada instruccion
		//si la direccion(indice) en memoria de un opearando en cualquier 
		//instruccion es 250-251 entonces se espera el input del keyboard!!

		case 0:
			// AND A,rf ; direct addressing ; A <- A and rf

			//this determines which register is used in the current instruction
			//this method gets the bits 10,9,8 of the IR that has the Regf number		
			regIndex = ir.getRf();
			//now we want to read that register 
			readResult = regs.read(regIndex);

			alu.and(readResult);
			break;

		case 1:
			// OR A,rf ; direct addressing ; A <- A or rf

			//this determines which register is used in the current instruction
			//this method gets the bits 10,9,8 of the IR that has the Regf number		
			regIndex = ir.getRf();
			//now we want to read that register 
			readResult = regs.read(regIndex);		

			alu.or(readResult);
			break;

		case 2:
			// XOR A,rf ; direct addressing ; A <- A xor rf		

			//this determines which register is used in the current instruction
			//this method gets the bits 10,9,8 of the IR that has the Regf number		
			regIndex = ir.getRf();
			//now we want to read that register 
			readResult = regs.read(regIndex);		

			alu.xor(readResult);		
			break;

		case 3:
			// ADDC A,rf ; direct addressing ; A <- A + (rf) + carry				
			//this determines which register is used in the current instruction
			//this method gets the bits 10,9,8 of the IR that has the Regf number		
			regIndex = ir.getRf();
			//now we want to read that register 
			readResult = regs.read(regIndex);	

			alu.addc(readResult);

			break;
			
		case 4:
			// MUL A,rf ; direct addressing ; A <- A + (rf) + carry				
			//this determines which register is used in the current instruction
			//this method gets the bits 10,9,8 of the IR that has the Regf number		
			regIndex = ir.getRf();
			//now we want to read that register that is a memory location 
			readResult = regs.read(regIndex);

			alu.mul(readResult);
			break;
		
				
		case 5:
			// DIV A,rf ; direct addressing ; A <- A / (rf) 				
			//this determines which register is used in the current instruction
			//this method gets the bits 10,9,8 of the IR that has the Regf number		
			regIndex = ir.getRf();
			//now we want to read that register that is a memory location 
			readResult = regs.read(regIndex);
			
			alu.div(readResult);
			break;

		case 6:
			// NEG A ; implicit addressing ; A <- not(A) twos complement 				
		
			alu.neg();
			break;
			
		case 7:
			// RLC A ; implicit addressing ; rotate left trough carry, the Accumulator			
		
			alu.rlc();
			break;
			
		case 8:
			// RRC A ; implicit addressing ; rotate right trough carry, the Accumulator
			
			alu.rrc();
			break;
			
		case 9:
			// DEC A ; implicit addressing ; A <- A-1
			
			alu.dec();
			break;

		case 10: 
			// LDA rf : load accumulator with register rf's content

			//this determines which register is used in the current instruction
			//this method gets the bits 10,9,8 of the IR that has the rf number
			regIndex = ir.getRf();
			//now we want to read that register
			register = regs.read(regIndex);

			acc.set(register);
			if( register[0] == 1 )
				sr.setNegative();
			else
				sr.clearNegative();


			break;

		case 11: 
			// STA rf : store accumulator's content in register rf

			//this determines which register is used in the current instruction
			regIndex = ir.getRf();
			
			regs.write( acc.get(), regIndex);
			break;

		case 12: 
			// LDA [addr] load accumulator from memory location address

			directAddress = ir.getDirectAddress();
            
			//if that address is 250 or 251 then wait for user's input
			if( directAddress == 250 || directAddress == 251){
				//the user can input at most one hex digit 
				String input = io.getKeyBoardInput();

				//convert string input to ASCII integer code then to its binary equivalent
				int[] x = converter.integerToBinary( converter.stringToASCII( input ) );
			
				x = unsignedExtend( x, 8 );
				for( int i = 0; i < x.length; i++ )
					System.out.print(x[i]);
				
				//store input in  memory address for keyboard
				mem.writeByte(x, directAddress);
				//store input in accumulator
				acc.set( x );
				
				if( x[0] == 1 )
					sr.setNegative();
				else
					sr.clearNegative();

			}
			
			else{
				//now to read from memory with address = regContent
				readResult = mem.readByte(directAddress);
				//storing the read value in the accumulator
				acc.set(readResult);
				
				if( readResult[0] == 1 )
					sr.setNegative();
				else
					sr.clearNegative();
			}
			
			break;

		case 13: 
			// STA [address] : store accumulator's content in memory location address

			
			directAddress = ir.getDirectAddress();

			if( directAddress >= 252 && directAddress <= 255 ){
				
				mem.writeByte( acc.get(), directAddress );
				
				if( mode == 1 )
				io.displayMonitorContent(mem);				
			}
			else
				//storing the accumulator's value in memory address found in the instruction
				mem.writeByte( acc.get(), directAddress  );
		
			break;

		case 14:
			// LDI A ; immediate addressing ; A <- Immediate
			//this determines which register is used in the current instruction
			//this method gets the bits 7 t 0 of the IR that has the immediate content		
			immediateContent = ir.getImmediateOperand();//we can use the previous method for code reuse since it has the same bits
			
			for( int x = 0; x < 8; x++)
				System.out.println(" " + immediateContent[x]);
			
		    this.acc.set(immediateContent);
		    
		    if( immediateContent[0] == 1 )
				sr.setNegative();
			else
				sr.clearNegative();
	
			break;
		case 16:
			/**el opcode que le sigue al 01110 = 14 es el 10000 = 16. 
			 *brinca a 16 para que todas las instrucciones de branch tengan opcode 100xx (eso me dijo nayda
			 *cuando le pregunte si ese brinco de 14 a 16 fue un error de ella) y tambien dijo que lo hizo asi
			 * para que sea mas facil dise~ar un componente de hardware de la proxima fase.
			 * **/
			// BRZ  ; implicit addressing ; if Z=1 then PC <- r7
			
			branchType.BRZ(this.regs.read(7));
			break;
			
		case 17:
            // BRC  ; implicit addressing ; if C=1 then PC <- r7
			
			branchType.BRC(this.regs.read(7));
			break;
		case 18:
            // BRN  ; implicit addressing ; if N=1 then PC <- r7
			
			branchType.BRN(this.regs.read(7));
			break;
		case 19:
            // BRO  ; implicit addressing; if O=1 then PC <- r7
			
			branchType.BRN(this.regs.read(7));
			break; 
		case 24: // NOP			case "24" esta correcto. nayda lo escogio asi
			break;		
		case 31: 
		
			//				// STOP			case "31" esta correcto. nayda lo escogio asi

			stopFlag = true;

			break;

		}



		//if debug-mode: wait for user to indicate that the next line of code should be executed 
		if( mode == 0 ){
			display();
			System.out.println("Please enter 0 to exit simulation or 1 to execute the next line of " +
			"code from the input file. ");
			Scanner in = new Scanner(System.in);
			int selection = in.nextInt();
			while( selection < 0 || selection > 1){
				System.out.println("INCORRECT SELECTION");
				System.out.println("Please enter 0 to exit simulation or 1 to execute the next line of " +
				"code from the input file. ");
				in = new Scanner(System.in);
				selection = in.nextInt();
			}	
			//if user wants to execute next instruction && the stop operation was not the previous operation 
			if( selection == 1 && stopFlag == false && instructionCount > 0)
				fetch(); //aki podemos aprevechar para darle display a todos los componenetes del micro luego
			// de la ejecucuion de la ultima instruccion
			else
				System.out.println("Simulation is finished.");

		} 
		else {
			//if the stop operation was not the previous operation
			if( stopFlag == false && instructionCount > 0 )
				//fetch the next instruction
				fetch(); //this call creates an infinite loop that will only stop if the STOP instruction is executed
			else
				System.out.println("Simulation is finished.");
		}

	}

	public void setInstructionCount(int wC){
		instructionCount = wC;
	}

	/**
	 * Thiis method performs a sign extension on a sequence of bits (an array of integers) 
	 * @param x array of integers (bits) to sign extend
	 * @param finalSize final size of array after extension
	 * @return the sign extended sequence of bits (array x)
	 */
	public int[] signedExtend( int[] x, int finalSize ){
		int[] newX = new int[finalSize];
		int bit = x[0]; //sign to extend
		//extend missing bits
		for( int i = 0; i < finalSize - x.length; i++ )
				newX[i] = bit;
		for( int k = finalSize - x.length, j=0 ; k < newX.length; k++, j++ )
				newX[k] = x[j];
		return newX;
	}
	
	/**
	 * Thiis method performs an unsigned extension on a sequence of bits (an array of integers) 
	 * @param x array of integers (bits) to sign extend
	 * @param finalSize final size of array after extension
	 * @return the unsigned extended sequence of bits (array x)
	 */
	public int[] unsignedExtend( int[] x, int finalSize ){
		int[] newX = new int[finalSize];
		int bit = 0;
		for( int i = 0; i < finalSize - x.length; i++ )
				newX[i] = bit;
		for( int k = finalSize - x.length, j=0 ; k < newX.length; k++, j++ )
				newX[k] = x[j];
		return newX;
	}
	
	/**
	 * This methos prints all the AR2RISC's component's contents.
	 */
	public void display(){
		int temp[];
		NumberConverter converter = new NumberConverter();
		System.out.println("\nPROGRAM COUNTER:");
		temp = pc.get();
		for(int i = 0; i < temp.length; i++ )
			System.out.print( temp[i] );

		System.out.println("\n\nINSTRUCTION REGISTER:");
		temp = ir.get();
		for(int i = 0; i < temp.length; i++ )
			System.out.print( temp[i] );

		System.out.println("\n\nSTATUS REGISTER:");
		temp = sr.get();
		for(int i = 0; i < temp.length; i++ )
			System.out.print( temp[i] );

		System.out.println("\n\nACCUMULATOR REGISTER:");
		temp = acc.get();
		for(int i = 0; i < temp.length; i++ )
			System.out.print( temp[i] );

		System.out.println("\n\nGENERAL PURPOSE REGISTERS:");
		for( int i = 0; i < 8; i++ ){
			temp = regs.read(i);
			System.out.print("R" + i +" : " );
			for( int j = 0; j < temp.length; j++ ){
				System.out.print( temp[j] );
			}
			System.out.println("\n");
		}

		System.out.println("\nKEYBOARD:");
		temp = mem.readByte(250);
		System.out.print( converter.ASCIIToChar( converter.binaryToDecimal( temp ) ) + " ");
		temp = mem.readByte(251);
		System.out.print( converter.ASCIIToChar( converter.binaryToDecimal( temp ) ) + " ");
		

		System.out.println("\n\nDISPLAY:");		
		for(int i = 252; i < 256; i++ ){
			temp = mem.readByte(i);
			System.out.print( converter.ASCIIToChar( converter.binaryToDecimal( temp ) ) + " ");
		}

		System.out.println("\nMAIN MEMORY: \n(address : content)");

		for( int i = 0; i < 255; i += 2 ){
			temp = mem.readByte(i);
			System.out.print(i + "-" + (i + 1) + " : " );
			System.out.print( converter.binaryToHex(temp) + " - ");
			temp = mem.readByte( i + 1 );
			System.out.print( converter.binaryToHex(temp) );
			System.out.println("\n");
		}
	}
	

	
}
